/**
 * 定时运行
 * @author 木人心
 * @version 1.0.0
 */
import {
  FnOperationControlConstructorOption,
  FnOperationControlFnCallbackParams
} from '@utils/object-util/periodic-execution/type'

export enum OperationStateEnum {
  STOP, RUN
}

export class FnOperationControl {
  // 需要运行的函数
  private fn: {(params: FnOperationControlFnCallbackParams, ...arg: any[]): Promise<any>} = null
  // 定时器实例setTimeout
  private timer: NodeJS.Timeout = null
  // 定时器间隔周期
  private timeout: number = null
  // 当前运行状态
  private state: OperationStateEnum = OperationStateEnum.RUN
  // 立即执行
  private rightRun = true
  // fn函数运行的参数
  private arg = []

  constructor (option: FnOperationControlConstructorOption) {
    Object.assign(this, option)
    // 如果初始配置为暂定，设置当前状态为停止，否则开始执行
    if (option.pause === true) this.state = OperationStateEnum.STOP
    this.state === OperationStateEnum.RUN && this.run(this.rightRun, this.arg)
  }

  /**
   * 循环运行函数
   * @param params
   * @private
   */
  private loopRun (params?: FnOperationControlFnCallbackParams) {
    const runId = this.runId
    const _params = Object.assign({ isFirstRun: false }, params || {})
    this.timer && clearTimeout(this.timer)
    this.fn(_params, ...this.arg).finally(async () => {
      this.timer = setTimeout(() => {
        this.state === OperationStateEnum.RUN && this.runId === runId && this.loopRun()
      }, this.timeout)
    })
  }

  /**
   * 手动运行函数，循环周期的起点
   * @param rightRun
   * @param arg
   */
  // 每次运行的唯一标识，保证保证异步运行的时能够准确找到运行次序
  private runId = 0
  public run (rightRun = true, arg?: any[]) {
    this.arg = arg || []
    this.state = OperationStateEnum.RUN
    this.runId += 1
    if (rightRun) {
      this.loopRun({ isFirstRun: true })
    } else {
      this.timer = setTimeout(() => {
        this.loopRun({ isFirstRun: true })
      }, this.timeout)
    }
  }

  /**
   * 停止周期执行
   */
  public stop () {
    clearTimeout(this.timer)
    this.state = OperationStateEnum.STOP
  }

  /**
   * 设置间隔时间
   * @param timeout
   */
  public setTimeout (timeout: number) {
    this.timeout = timeout
  }
}
